www.gusucode.com > Piwik 网站流量统计系统 v2.9.1PHP源码程序 > Piwik 网站流量统计系统 v2.9.1/piwik/piwik/core/Plugin/Manager.php
<?php /** * Piwik - free/libre analytics platform * * @link http://piwik.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later * */ namespace Piwik\Plugin; use Piwik\Cache\PersistentCache; use Piwik\CacheFile; use Piwik\Columns\Dimension; use Piwik\Common; use Piwik\Config as PiwikConfig; use Piwik\Config; use Piwik\Db; use Piwik\Development; use Piwik\EventDispatcher; use Piwik\Filesystem; use Piwik\Log; use Piwik\Option; use Piwik\Piwik; use Piwik\Plugin; use Piwik\Singleton; use Piwik\Theme; use Piwik\Tracker; use Piwik\Translate; use Piwik\Updater; use Piwik\SettingsServer; use Piwik\Plugin\Dimension\ActionDimension; use Piwik\Plugin\Dimension\ConversionDimension; use Piwik\Plugin\Dimension\VisitDimension; require_once PIWIK_INCLUDE_PATH . '/core/EventDispatcher.php'; /** * The singleton that manages plugin loading/unloading and installation/uninstallation. * * @method static Manager getInstance() */ class Manager extends Singleton { protected $pluginsToLoad = array(); protected $doLoadPlugins = true; /** * @var Plugin[] */ protected $loadedPlugins = array(); /** * Default theme used in Piwik. */ const DEFAULT_THEME = "Morpheus"; protected $doLoadAlwaysActivatedPlugins = true; // These are always activated and cannot be deactivated protected $pluginToAlwaysActivate = array( 'CoreHome', 'CoreUpdater', 'CoreAdminHome', 'CoreConsole', 'CorePluginsAdmin', 'CoreVisualizations', 'Installation', 'SitesManager', 'UsersManager', 'API', 'Proxy', 'LanguagesManager', // default Piwik theme, always enabled self::DEFAULT_THEME, ); // Plugins bundled with core package, disabled by default protected $corePluginsDisabledByDefault = array( 'DBStats', 'ExampleCommand', 'ExampleSettingsPlugin', 'ExampleUI', 'ExampleVisualization', 'ExamplePluginTemplate', 'ExampleTracker', 'ExampleReport' ); // Themes bundled with core package, disabled by default protected $coreThemesDisabledByDefault = array( 'ExampleTheme' ); /** * Loads plugin that are enabled */ public function loadActivatedPlugins() { $pluginsToLoad = Config::getInstance()->Plugins['Plugins']; $this->loadPlugins($pluginsToLoad); } /** * Called during Tracker */ public function loadCorePluginsDuringTracker() { $pluginsToLoad = Config::getInstance()->Plugins['Plugins']; $pluginsToLoad = array_diff($pluginsToLoad, Tracker::getPluginsNotToLoad()); $this->loadPlugins($pluginsToLoad); } /** * @return array names of plugins that have been loaded */ public function loadTrackerPlugins() { $cache = new PersistentCache('PluginsTracker'); if ($cache->has()) { $pluginsTracker = $cache->get(); } else { $this->unloadPlugins(); $this->loadActivatedPlugins(); $pluginsTracker = array(); foreach ($this->loadedPlugins as $pluginName => $plugin) { if ($this->isTrackerPlugin($plugin)) { $pluginsTracker[] = $pluginName; } } if (!empty($pluginsTracker)) { $cache->set($pluginsTracker); } } $this->unloadPlugins(); if (empty($pluginsTracker)) { return array(); } $pluginsTracker = array_diff($pluginsTracker, Tracker::getPluginsNotToLoad()); $this->doNotLoadAlwaysActivatedPlugins(); $this->loadPlugins($pluginsTracker); return $pluginsTracker; } public function getCorePluginsDisabledByDefault() { return array_merge( $this->corePluginsDisabledByDefault, $this->coreThemesDisabledByDefault); } // If a plugin hooks onto at least an event starting with "Tracker.", we load the plugin during tracker const TRACKER_EVENT_PREFIX = 'Tracker.'; /** * @param $pluginName * @return bool */ public function isPluginOfficialAndNotBundledWithCore($pluginName) { static $gitModules; if (empty($gitModules)) { $gitModules = file_get_contents(PIWIK_INCLUDE_PATH . '/.gitmodules'); } // All submodules are officially maintained plugins $isSubmodule = false !== strpos($gitModules, "plugins/" . $pluginName . "\n"); return $isSubmodule; } /** * Update Plugins config * * @param array $plugins Plugins */ private function updatePluginsConfig($pluginsToLoad) { $section = PiwikConfig::getInstance()->Plugins; $section['Plugins'] = $pluginsToLoad; PiwikConfig::getInstance()->Plugins = $section; } /** * Update PluginsInstalled config * * @param array $plugins Plugins */ private function updatePluginsInstalledConfig($plugins) { $section = PiwikConfig::getInstance()->PluginsInstalled; $section['PluginsInstalled'] = $plugins; PiwikConfig::getInstance()->PluginsInstalled = $section; } public function clearPluginsInstalledConfig() { $this->updatePluginsInstalledConfig( array() ); PiwikConfig::getInstance()->forceSave(); PiwikConfig::getInstance()->init(); } /** * Returns true if plugin is always activated * * @param string $name Name of plugin * @return bool */ private function isPluginAlwaysActivated($name) { return in_array($name, $this->pluginToAlwaysActivate); } /** * Returns true if the plugin can be uninstalled. Any non-core plugin can be uninstalled. * * @param $name * @return bool */ private function isPluginUninstallable($name) { return !$this->isPluginBundledWithCore($name); } /** * Returns `true` if a plugin has been activated. * * @param string $name Name of plugin, eg, `'Actions'`. * @return bool * @api */ public function isPluginActivated($name) { return in_array($name, $this->pluginsToLoad) || $this->isPluginAlwaysActivated($name); } /** * Returns `true` if plugin is loaded (in memory). * * @param string $name Name of plugin, eg, `'Acions'`. * @return bool * @api */ public function isPluginLoaded($name) { return isset($this->loadedPlugins[$name]); } /** * Reads the directories inside the plugins/ directory and returns their names in an array * * @return array */ public function readPluginsDirectory() { $pluginsName = _glob(self::getPluginsDirectory() . '*', GLOB_ONLYDIR); $result = array(); if ($pluginsName != false) { foreach ($pluginsName as $path) { if (self::pluginStructureLooksValid($path)) { $result[] = basename($path); } } } return $result; } public static function getPluginsDirectory() { return PIWIK_INCLUDE_PATH . '/plugins/'; } /** * Deactivate plugin * * @param string $pluginName Name of plugin */ public function deactivatePlugin($pluginName) { // execute deactivate() to let the plugin do cleanups $this->executePluginDeactivate($pluginName); $this->unloadPluginFromMemory($pluginName); $this->removePluginFromConfig($pluginName); $this->clearCache($pluginName); /** * Event triggered after a plugin has been deactivated. * * @param string $pluginName The plugin that has been deactivated. */ Piwik::postEvent('PluginManager.pluginDeactivated', array($pluginName)); } /** * Tries to find the given components such as a Menu or Tasks implemented by plugins. * This method won't cache the found components. If you need to find the same component multiple times you might * want to cache the result to save a tiny bit of time. * * @param string $componentName The name of the component you want to look for. In case you request a * component named 'Menu' it'll look for a file named 'Menu.php' within the * root of all plugin folders that implement a class named * Piwik\Plugin\$PluginName\Menu. * @param string $expectedSubclass If not empty, a check will be performed whether a found file extends the * given subclass. If the requested file exists but does not extend this class * a warning will be shown to advice a developer to extend this certain class. * * @return \stdClass[] */ public function findComponents($componentName, $expectedSubclass) { $plugins = $this->getPluginsLoadedAndActivated(); $components = array(); foreach ($plugins as $plugin) { $component = $plugin->findComponent($componentName, $expectedSubclass); if (!empty($component)) { $components[] = $component; } } return $components; } public function findMultipleComponents($directoryWithinPlugin, $expectedSubclass) { $plugins = $this->getPluginsLoadedAndActivated(); $found = array(); foreach ($plugins as $plugin) { $components = $plugin->findMultipleComponents($directoryWithinPlugin, $expectedSubclass); if (!empty($components)) { $found = array_merge($found, $components); } } return $found; } /** * Uninstalls a Plugin (deletes plugin files from the disk) * Only deactivated plugins can be uninstalled * * @param $pluginName * @throws \Exception * @return bool */ public function uninstallPlugin($pluginName) { if ($this->isPluginLoaded($pluginName)) { throw new \Exception("To uninstall the plugin $pluginName, first disable it in Piwik > Settings > Plugins"); } $this->loadAllPluginsAndGetTheirInfo(); \Piwik\Settings\Manager::cleanupPluginSettings($pluginName); $this->executePluginDeactivate($pluginName); $this->executePluginUninstall($pluginName); $this->removePluginFromPluginsInstalledConfig($pluginName); $this->unloadPluginFromMemory($pluginName); $this->removePluginFromConfig($pluginName); $this->removeInstalledVersionFromOptionTable($pluginName); $this->clearCache($pluginName); self::deletePluginFromFilesystem($pluginName); if ($this->isPluginInFilesystem($pluginName)) { return false; } return true; } /** * @param string $pluginName */ private function clearCache($pluginName) { Filesystem::deleteAllCacheOnUpdate($pluginName); } public static function deletePluginFromFilesystem($plugin) { Filesystem::unlinkRecursive(PIWIK_INCLUDE_PATH . '/plugins/' . $plugin, $deleteRootToo = true); } /** * Install loaded plugins * * @throws * @return array Error messages of plugin install fails */ public function installLoadedPlugins() { Log::verbose("Loaded plugins: " . implode(", ", array_keys($this->getLoadedPlugins()))); $messages = array(); foreach ($this->getLoadedPlugins() as $plugin) { try { $this->installPluginIfNecessary($plugin); } catch (\Exception $e) { $messages[] = $e->getMessage(); } } return $messages; } /** * Activate the specified plugin and install (if needed) * * @param string $pluginName Name of plugin * @throws \Exception */ public function activatePlugin($pluginName) { $plugins = PiwikConfig::getInstance()->Plugins['Plugins']; if (in_array($pluginName, $plugins)) { throw new \Exception("Plugin '$pluginName' already activated."); } if (!$this->isPluginInFilesystem($pluginName)) { throw new \Exception("Plugin '$pluginName' cannot be found in the filesystem in plugins/ directory."); } $this->deactivateThemeIfTheme($pluginName); // Load plugin $plugin = $this->loadPlugin($pluginName); if ($plugin === null) { throw new \Exception("The plugin '$pluginName' was found in the filesystem, but could not be loaded.'"); } $this->installPluginIfNecessary($plugin); $plugin->activate(); EventDispatcher::getInstance()->postPendingEventsTo($plugin); $this->pluginsToLoad[] = $pluginName; $this->updatePluginsConfig($this->pluginsToLoad); PiwikConfig::getInstance()->forceSave(); $this->clearCache($pluginName); /** * Event triggered after a plugin has been activated. * * @param string $pluginName The plugin that has been activated. */ Piwik::postEvent('PluginManager.pluginActivated', array($pluginName)); } protected function isPluginInFilesystem($pluginName) { $existingPlugins = $this->readPluginsDirectory(); $isPluginInFilesystem = array_search($pluginName, $existingPlugins) !== false; return Filesystem::isValidFilename($pluginName) && $isPluginInFilesystem; } /** * Returns the currently enabled theme. * * If no theme is enabled, the **Morpheus** plugin is returned (this is the base and default theme). * * @return Plugin * @api */ public function getThemeEnabled() { $plugins = $this->getLoadedPlugins(); $theme = false; foreach ($plugins as $plugin) { /* @var $plugin Plugin */ if ($plugin->isTheme() && $this->isPluginActivated($plugin->getPluginName()) ) { if ($plugin->getPluginName() != self::DEFAULT_THEME) { return $plugin; // enabled theme (not default) } $theme = $plugin; // default theme } } return $theme; } /** * @param string $themeName * @throws \Exception * @return Theme */ public function getTheme($themeName) { $plugins = $this->getLoadedPlugins(); foreach ($plugins as $plugin) { if ($plugin->isTheme() && $plugin->getPluginName() == $themeName) { return new Theme($plugin); } } throw new \Exception('Theme not found : ' . $themeName); } public function getNumberOfActivatedPlugins() { $counter = 0; $pluginNames = $this->getLoadedPluginsName(); foreach ($pluginNames as $pluginName) { if ($this->isPluginActivated($pluginName)) { $counter++; } } return $counter; } /** * Returns info regarding all plugins. Loads plugins that can be loaded. * * @return array An array that maps plugin names with arrays of plugin information. Plugin * information consists of the following entries: * * - **activated**: Whether the plugin is activated. * - **alwaysActivated**: Whether the plugin should always be activated, * or not. * - **uninstallable**: Whether the plugin is uninstallable or not. * - **invalid**: If the plugin is invalid, this property will be set to true. * If the plugin is not invalid, this property will not exist. * - **info**: If the plugin was loaded, will hold the plugin information. * See {@link Piwik\Plugin::getInformation()}. * @api */ public function loadAllPluginsAndGetTheirInfo() { $language = Translate::getLanguageToLoad(); $plugins = array(); $listPlugins = array_merge( $this->readPluginsDirectory(), PiwikConfig::getInstance()->Plugins['Plugins'] ); $listPlugins = array_unique($listPlugins); foreach ($listPlugins as $pluginName) { // Hide plugins that are never going to be used if ($this->isPluginBogus($pluginName)) { continue; } // If the plugin is not core and looks bogus, do not load if ($this->isPluginThirdPartyAndBogus($pluginName)) { $info = array( 'invalid' => true, 'activated' => false, 'alwaysActivated' => false, 'uninstallable' => true, ); } else { $this->loadTranslation($pluginName, $language); $this->loadPlugin($pluginName); $info = array( 'activated' => $this->isPluginActivated($pluginName), 'alwaysActivated' => $this->isPluginAlwaysActivated($pluginName), 'uninstallable' => $this->isPluginUninstallable($pluginName), ); } $plugins[$pluginName] = $info; } $this->loadPluginTranslations(); $loadedPlugins = $this->getLoadedPlugins(); foreach ($loadedPlugins as $oPlugin) { $pluginName = $oPlugin->getPluginName(); $info = array( 'info' => $oPlugin->getInformation(), 'activated' => $this->isPluginActivated($pluginName), 'alwaysActivated' => $this->isPluginAlwaysActivated($pluginName), 'missingRequirements' => $oPlugin->getMissingDependencies(), 'uninstallable' => $this->isPluginUninstallable($pluginName), ); $plugins[$pluginName] = $info; } return $plugins; } protected static function isManifestFileFound($path) { return file_exists($path . "/" . MetadataLoader::PLUGIN_JSON_FILENAME); } /** * Returns `true` if the plugin is bundled with core or `false` if it is third party. * * @param string $name The name of the plugin, eg, `'Actions'`. * @return bool */ public function isPluginBundledWithCore($name) { // Reading the plugins from the global.ini.php config file $pluginsBundledWithPiwik = PiwikConfig::getInstance()->getFromGlobalConfig('Plugins'); $pluginsBundledWithPiwik = $pluginsBundledWithPiwik['Plugins']; return (!empty($pluginsBundledWithPiwik) && in_array($name, $pluginsBundledWithPiwik)) || in_array($name, $this->getCorePluginsDisabledByDefault()) || $name == self::DEFAULT_THEME; } protected function isPluginThirdPartyAndBogus($pluginName) { if ($this->isPluginBundledWithCore($pluginName)) { return false; } if ($this->isPluginBogus($pluginName)) { return true; } $path = $this->getPluginsDirectory() . $pluginName; if (!$this->isManifestFileFound($path)) { return true; } return false; } /** * Load AND activates the specified plugins. It will also overwrite all previously loaded plugins, so it acts * as a setter. * * @param array $pluginsToLoad Array of plugins to load. */ public function loadPlugins(array $pluginsToLoad) { $pluginsToLoad = array_unique($pluginsToLoad); $this->pluginsToLoad = $pluginsToLoad; $this->reloadActivatedPlugins(); } /** * Disable plugin loading. */ public function doNotLoadPlugins() { $this->doLoadPlugins = false; } /** * Disable loading of "always activated" plugins. */ public function doNotLoadAlwaysActivatedPlugins() { $this->doLoadAlwaysActivatedPlugins = false; } /** * Load translations for loaded plugins * * @param bool|string $language Optional language code */ public function loadPluginTranslations($language = false) { if (empty($language)) { $language = Translate::getLanguageToLoad(); } $cache = new CacheFile('tracker', 43200); // ttl=12hours $cacheKey = 'PluginTranslations'; if (!empty($language)) { $cacheKey .= '-' . trim($language); } if (!empty($this->loadedPlugins)) { // makes sure to create a translation in case loaded plugins change (ie Tests vs Tracker vs UI etc) $cacheKey .= '-' . md5(implode('', $this->getLoadedPluginsName())); } $translations = $cache->get($cacheKey); if (!empty($translations) && is_array($translations) && !Development::isEnabled()) { Translate::mergeTranslationArray($translations); return; } $translations = array(); $pluginNames = self::getAllPluginsNames(); foreach ($pluginNames as $pluginName) { if ($this->isPluginLoaded($pluginName) || $this->isPluginBundledWithCore($pluginName)) { $this->loadTranslation($pluginName, $language); if (isset($GLOBALS['Piwik_translations'][$pluginName])) { $translations[$pluginName] = $GLOBALS['Piwik_translations'][$pluginName]; } } } $cache->set($cacheKey, $translations); } /** * Execute postLoad() hook for loaded plugins */ public function postLoadPlugins() { $plugins = $this->getLoadedPlugins(); foreach ($plugins as $plugin) { $plugin->postLoad(); } } /** * Returns an array containing the plugins class names (eg. 'UserCountry' and NOT 'UserCountry') * * @return array */ public function getLoadedPluginsName() { return array_keys($this->getLoadedPlugins()); } /** * Returns an array mapping loaded plugin names with their plugin objects, eg, * * array( * 'UserCountry' => Plugin $pluginObject, * 'UserSettings' => Plugin $pluginObject, * ); * * @return Plugin[] */ public function getLoadedPlugins() { return $this->loadedPlugins; } /** * @param string $piwikVersion * @return Plugin[] */ public function getIncompatiblePlugins($piwikVersion) { $plugins = $this->getLoadedPlugins(); $incompatible = array(); foreach ($plugins as $plugin) { if ($plugin->hasMissingDependencies($piwikVersion)) { $incompatible[] = $plugin; } } return $incompatible; } /** * Returns an array of plugins that are currently loaded and activated, * mapping loaded plugin names with their plugin objects, eg, * * array( * 'UserCountry' => Plugin $pluginObject, * 'UserSettings' => Plugin $pluginObject, * ); * * @return Plugin[] */ public function getPluginsLoadedAndActivated() { $plugins = $this->getLoadedPlugins(); $enabled = $this->getActivatedPlugins(); if (empty($enabled)) { return array(); } $enabled = array_combine($enabled, $enabled); $plugins = array_intersect_key($plugins, $enabled); return $plugins; } /** * Returns a list of all names of currently activated plugin eg, * * array( * 'UserCountry' * 'UserSettings' * ); * * @return string[] */ public function getActivatedPlugins() { return $this->pluginsToLoad; } /** * Returns a Plugin object by name. * * @param string $name The name of the plugin, eg, `'Actions'`. * @throws \Exception If the plugin has not been loaded. * @return Plugin */ public function getLoadedPlugin($name) { if (!isset($this->loadedPlugins[$name])) { throw new \Exception("The plugin '$name' has not been loaded."); } return $this->loadedPlugins[$name]; } /** * Load the plugins classes installed. * Register the observers for every plugin. */ private function reloadActivatedPlugins() { if ($this->doLoadAlwaysActivatedPlugins) { $this->pluginsToLoad = array_merge($this->pluginsToLoad, $this->pluginToAlwaysActivate); } $this->pluginsToLoad = array_unique($this->pluginsToLoad); $pluginsToPostPendingEventsTo = array(); foreach ($this->pluginsToLoad as $pluginName) { if (!$this->isPluginLoaded($pluginName) && !$this->isPluginThirdPartyAndBogus($pluginName) ) { $newPlugin = $this->loadPlugin($pluginName); if ($newPlugin === null) { continue; } if ($newPlugin->hasMissingDependencies()) { $this->deactivatePlugin($pluginName); continue; } $pluginsToPostPendingEventsTo[] = $newPlugin; } } // post pending events after all plugins are successfully loaded foreach ($pluginsToPostPendingEventsTo as $plugin) { EventDispatcher::getInstance()->postPendingEventsTo($plugin); } } public function getIgnoredBogusPlugins() { $ignored = array(); foreach ($this->pluginsToLoad as $pluginName) { if ($this->isPluginThirdPartyAndBogus($pluginName)) { $ignored[] = $pluginName; } } return $ignored; } /** * Returns the name of all plugins found in this Piwik instance * (including those not enabled and themes) * * @return array */ public static function getAllPluginsNames() { $pluginsToLoad = array_merge( PiwikConfig::getInstance()->Plugins['Plugins'], self::getInstance()->readPluginsDirectory(), self::getInstance()->getCorePluginsDisabledByDefault() ); $pluginsToLoad = array_values(array_unique($pluginsToLoad)); return $pluginsToLoad; } /** * Loads the plugin filename and instantiates the plugin with the given name, eg. UserCountry. * Contrary to loadPlugins() it does not activate the plugin, it only loads it. * * @param string $pluginName * @throws \Exception * @return Plugin|null */ public function loadPlugin($pluginName) { if (isset($this->loadedPlugins[$pluginName])) { return $this->loadedPlugins[$pluginName]; } $newPlugin = $this->makePluginClass($pluginName); $this->addLoadedPlugin($pluginName, $newPlugin); return $newPlugin; } /** * @param $pluginName * @return Plugin * @throws \Exception */ protected function makePluginClass($pluginName) { $pluginFileName = sprintf("%s/%s.php", $pluginName, $pluginName); $pluginClassName = $pluginName; if (!Filesystem::isValidFilename($pluginName)) { throw new \Exception(sprintf("The plugin filename '%s' is not a valid filename", $pluginFileName)); } $path = self::getPluginsDirectory() . $pluginFileName; if (!file_exists($path)) { // Create the smallest minimal Piwik Plugin // Eg. Used for Morpheus default theme which does not have a Morpheus.php file return new Plugin($pluginName); } require_once $path; $namespacedClass = $this->getClassNamePlugin($pluginName); if (!class_exists($namespacedClass, false)) { throw new \Exception("The class $pluginClassName couldn't be found in the file '$path'"); } $newPlugin = new $namespacedClass; if (!($newPlugin instanceof Plugin)) { throw new \Exception("The plugin $pluginClassName in the file $path must inherit from Plugin."); } return $newPlugin; } protected function getClassNamePlugin($pluginName) { $className = $pluginName; if ($pluginName == 'API') { $className = 'Plugin'; } return "\\Piwik\\Plugins\\$pluginName\\$className"; } /** * Unload plugin * * @param Plugin|string $plugin * @throws \Exception */ public function unloadPlugin($plugin) { if (!($plugin instanceof Plugin)) { $oPlugin = $this->loadPlugin($plugin); if ($oPlugin === null) { unset($this->loadedPlugins[$plugin]); return; } $plugin = $oPlugin; } unset($this->loadedPlugins[$plugin->getPluginName()]); } /** * Unload all loaded plugins */ public function unloadPlugins() { $pluginsLoaded = $this->getLoadedPlugins(); foreach ($pluginsLoaded as $plugin) { $this->unloadPlugin($plugin); } } /** * Install a specific plugin * * @param Plugin $plugin * @throws \Piwik\Plugin\PluginException if installation fails */ private function executePluginInstall(Plugin $plugin) { try { $plugin->install(); } catch (\Exception $e) { throw new \Piwik\Plugin\PluginException($plugin->getPluginName(), $e->getMessage()); } } /** * Add a plugin in the loaded plugins array * * @param string $pluginName plugin name without prefix (eg. 'UserCountry') * @param Plugin $newPlugin */ private function addLoadedPlugin($pluginName, Plugin $newPlugin) { $this->loadedPlugins[$pluginName] = $newPlugin; } /** * Load translation * * @param Plugin $plugin * @param string $langCode * @throws \Exception * @return bool whether the translation was found and loaded */ private function loadTranslation($plugin, $langCode) { // we are in Tracker mode if Loader is not (yet) loaded if (SettingsServer::isTrackerApiRequest()) { return false; } if (is_string($plugin)) { $pluginName = $plugin; } else { $pluginName = $plugin->getPluginName(); } $path = self::getPluginsDirectory() . $pluginName . '/lang/%s.json'; $defaultLangPath = sprintf($path, $langCode); $defaultEnglishLangPath = sprintf($path, 'en'); $translationsLoaded = false; // merge in english translations as default first if (file_exists($defaultEnglishLangPath)) { $translations = $this->getTranslationsFromFile($defaultEnglishLangPath); $translationsLoaded = true; if (isset($translations[$pluginName])) { // only merge translations of plugin - prevents overwritten strings Translate::mergeTranslationArray(array($pluginName => $translations[$pluginName])); } } // merge in specific language translations (to overwrite english defaults) if (!empty($langCode) && $defaultEnglishLangPath != $defaultLangPath && file_exists($defaultLangPath)) { $translations = $this->getTranslationsFromFile($defaultLangPath); $translationsLoaded = true; if (isset($translations[$pluginName])) { // only merge translations of plugin - prevents overwritten strings Translate::mergeTranslationArray(array($pluginName => $translations[$pluginName])); } } return $translationsLoaded; } /** * Return names of all installed plugins. * * @return array * @api */ public function getInstalledPluginsName() { $pluginNames = PiwikConfig::getInstance()->PluginsInstalled['PluginsInstalled']; return $pluginNames; } /** * Returns names of plugins that should be loaded, but cannot be since their * files cannot be found. * * @return array * @api */ public function getMissingPlugins() { $missingPlugins = array(); if (isset(PiwikConfig::getInstance()->Plugins['Plugins'])) { $plugins = PiwikConfig::getInstance()->Plugins['Plugins']; foreach ($plugins as $pluginName) { // if a plugin is listed in the config, but is not loaded, it does not exist in the folder if (!self::getInstance()->isPluginLoaded($pluginName) && !$this->isPluginBogus($pluginName) ) { $missingPlugins[] = $pluginName; } } } return $missingPlugins; } /** * Install a plugin, if necessary * * @param Plugin $plugin */ private function installPluginIfNecessary(Plugin $plugin) { $pluginName = $plugin->getPluginName(); $saveConfig = false; // is the plugin already installed or is it the first time we activate it? $pluginsInstalled = $this->getInstalledPluginsName(); if (!$this->isPluginInstalled($pluginName)) { $this->executePluginInstall($plugin); $pluginsInstalled[] = $pluginName; $this->updatePluginsInstalledConfig($pluginsInstalled); Updater::recordComponentSuccessfullyUpdated($plugin->getPluginName(), $plugin->getVersion()); $saveConfig = true; } if ($saveConfig) { PiwikConfig::getInstance()->forceSave(); } } public function isTrackerPlugin(Plugin $plugin) { $dimensions = VisitDimension::getDimensions($plugin); if (!empty($dimensions)) { return true; } $dimensions = ActionDimension::getDimensions($plugin); if (!empty($dimensions)) { return true; } $hooks = $plugin->getListHooksRegistered(); $hookNames = array_keys($hooks); foreach ($hookNames as $name) { if (strpos($name, self::TRACKER_EVENT_PREFIX) === 0) { return true; } if ($name === 'Request.initAuthenticationObject') { return true; } } $dimensions = ConversionDimension::getDimensions($plugin); if (!empty($dimensions)) { return true; } return false; } private static function pluginStructureLooksValid($path) { $name = basename($path); return file_exists($path . "/" . $name . ".php") || self::isManifestFileFound($path); } /** * @param $pluginName */ private function removePluginFromPluginsInstalledConfig($pluginName) { $pluginsInstalled = PiwikConfig::getInstance()->PluginsInstalled['PluginsInstalled']; $key = array_search($pluginName, $pluginsInstalled); if ($key !== false) { unset($pluginsInstalled[$key]); } $this->updatePluginsInstalledConfig($pluginsInstalled); } /** * @param $pluginName */ private function removePluginFromPluginsConfig($pluginName) { $pluginsEnabled = PiwikConfig::getInstance()->Plugins['Plugins']; $key = array_search($pluginName, $pluginsEnabled); if ($key !== false) { unset($pluginsEnabled[$key]); } $this->updatePluginsConfig($pluginsEnabled); } /** * @param string $pathToTranslationFile * @throws \Exception * @return mixed */ private function getTranslationsFromFile($pathToTranslationFile) { $data = file_get_contents($pathToTranslationFile); $translations = json_decode($data, true); if (is_null($translations) && Common::hasJsonErrorOccurred()) { $jsonError = Common::getLastJsonError(); $message = sprintf('Not able to load translation file %s: %s', $pathToTranslationFile, $jsonError); throw new \Exception($message); } return $translations; } /** * @param $pluginName * @return bool */ private function isPluginBogus($pluginName) { $bogusPlugins = array( 'PluginMarketplace', //defines a plugin.json but 1.x Piwik plugin 'DoNotTrack', // Removed in 2.0.3 'AnonymizeIP', // Removed in 2.0.3 ); return in_array($pluginName, $bogusPlugins); } private function deactivateThemeIfTheme($pluginName) { // Only one theme enabled at a time $themeEnabled = $this->getThemeEnabled(); if ($themeEnabled && $themeEnabled->getPluginName() != self::DEFAULT_THEME) { $themeAlreadyEnabled = $themeEnabled->getPluginName(); $plugin = $this->loadPlugin($pluginName); if ($plugin->isTheme()) { $this->deactivatePlugin($themeAlreadyEnabled); } } } /** * @param $pluginName */ private function executePluginDeactivate($pluginName) { if (!$this->isPluginBogus($pluginName)) { $plugin = $this->loadPlugin($pluginName); if ($plugin !== null) { $plugin->deactivate(); } } } /** * @param $pluginName */ private function unloadPluginFromMemory($pluginName) { $key = array_search($pluginName, $this->pluginsToLoad); if ($key !== false) { unset($this->pluginsToLoad[$key]); } } /** * @param $pluginName */ private function removePluginFromConfig($pluginName) { $this->removePluginFromPluginsConfig($pluginName); PiwikConfig::getInstance()->forceSave(); } /** * @param $pluginName */ private function executePluginUninstall($pluginName) { try { $plugin = $this->getLoadedPlugin($pluginName); $plugin->uninstall(); } catch (\Exception $e) { } if (empty($plugin)) { return; } try { foreach (VisitDimension::getDimensions($plugin) as $dimension) { $this->uninstallDimension($dimension); } } catch (\Exception $e) { } try { foreach (ActionDimension::getDimensions($plugin) as $dimension) { $this->uninstallDimension($dimension); } } catch (\Exception $e) { } try { foreach (ConversionDimension::getDimensions($plugin) as $dimension) { $this->uninstallDimension($dimension); } } catch (\Exception $e) { } } /** * @param ConversionDimension|VisitDimension|ActionDimension $dimension */ private function uninstallDimension(Dimension $dimension) { $dimension->uninstall(); Option::delete('version_' . $dimension->getVersion()); } /** * @param $pluginName * @return bool */ public function isPluginInstalled($pluginName) { $pluginsInstalled = $this->getInstalledPluginsName(); return in_array($pluginName, $pluginsInstalled); } private function removeInstalledVersionFromOptionTable($version) { Option::delete('version_' . $version); } } /** */ class PluginException extends \Exception { function __construct($pluginName, $message) { parent::__construct("There was a problem installing the plugin " . $pluginName . ": " . $message . " If this plugin has already been installed, and if you want to hide this message</b>, you must add the following line under the [PluginsInstalled] entry in your config/config.ini.php file: PluginsInstalled[] = $pluginName"); } }